package com.xqboss.framework.aspectj;

import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.SecureUtil;
import com.alibaba.fastjson.JSONObject;
import com.xqboss.common.annotation.CacheRedis;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.util.concurrent.TimeUnit;

/**
 * @author by 木鱼
 * @date 2019/11/15.
 */
@Aspect
@Component
@Slf4j
@RequiredArgsConstructor
public class CacheRedisAspect {

    private final RedisTemplate<Object,Object> redisTemplate;
    private final RedissonClient redissonClient;

    @Pointcut(value = "@annotation(com.xqboss.common.annotation.CacheRedis)")
    public void pointCut() {
    }

    @Around(value = "pointCut()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        //获取method对象
        Method method = signature.getMethod();
        CacheRedis cacheRedis = method.getAnnotation(CacheRedis.class);
        int expireTime = cacheRedis.expireTime();
        Object[] args = joinPoint.getArgs();
        //创建key
        String key = createKey(signature, args);
        String KeyPrivate = StrUtil.isBlank(cacheRedis.value()) ? "cache:" + signature.getName() + ":" + signature.getMethod().getName() + ":" : cacheRedis.value();
        Object redisValue = redisTemplate.opsForValue().get(KeyPrivate + ":" + key);
        if (null != redisValue) {
            return redisValue;
        }
        // 初始化分布式锁
        RLock lock = redissonClient.getLock("lock:" + KeyPrivate + ":" + key);
        try {
            // 防止缓存击穿 加锁
            lock.lock(cacheRedis.lockTime(), TimeUnit.MILLISECONDS);
            // 再次检查内存是否有，因为高并发下，可能在加锁这段时间内，已有其他线程放入缓存
            redisValue = redisTemplate.opsForValue().get(KeyPrivate + ":" + key);
        } finally {
            if (redisValue != null) {
                unlock(lock, KeyPrivate);
                return redisValue;
            }
        }
        try {
            // 并把结果放入缓存
            // 2. 执行查询的业务逻辑从数据库查询
            log.info("入库执行，缓存为空：key:{}", key);
            redisValue = joinPoint.proceed(joinPoint.getArgs());
            redisTemplate.opsForValue().set(KeyPrivate + ":" + key, redisValue, expireTime, TimeUnit.SECONDS);
            lock.unlock();
            return redisValue;
        } catch (Exception e) {
            unlock(lock, KeyPrivate);
            throw e;
        }
    }

    private void unlock(RLock lock, String KeyPrivate) {
        try {
            if (lock.isLocked()) {
                lock.unlock();
            }
        } catch (Exception e) {
            log.error("缓存工具解锁失败，可能出现了慢查询,KeyPrivate：{}", KeyPrivate, e);
        }
    }

    /**
     * 拼接md5参数key
     */
    private String createKey(MethodSignature signature, Object[] args) {
        String[] parameterNames = signature.getParameterNames();
        if (null == parameterNames) {
            return "";
        }
        int length = parameterNames.length;
        JSONObject jsonObject = new JSONObject(true);
        for (int i = 0; i < length; i++) {
            jsonObject.put(parameterNames[i], args[i]);
        }
        return SecureUtil.md5(jsonObject.toJSONString());
    }

}
